package com.witmore.skyline.web.service.third.aliyun;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * 简单的QPS限流器
 * 每秒自动补充指定数量的令牌，真正实现QPS限制
 *
 * @author 豆子高
 * @date 2025-08-25
 */
@Slf4j
public class SimpleRateLimiter {

    /**
     * QPS限制（每秒令牌数）
     */
    @Getter
    private final int qps;

    /**
     * 当前令牌数量（整数）
     */
    private int tokens;

    /**
     * 上次补充令牌的时间戳（毫秒）
     */
    private long lastRefillTime;

    /**
     * 总请求数（用于监控）
     */
    private long totalRequests = 0;

    /**
     * 成功请求数（用于监控）
     */
    private long successRequests = 0;

    /**
     * 上次统计日志时间
     */
    private long lastStatsTime;

    /**
     * 默认超时时间（毫秒）
     */
    @Getter
    private final long defaultTimeoutMs;

    /**
     * 同步锁对象
     */
    private final Object lock = new Object();

    /**
     * 构造函数
     *
     * @param qps 每秒允许的请求数
     */
    public SimpleRateLimiter(int qps) {
        this(qps, 10000); // 默认10秒超时
    }

    /**
     * 构造函数
     *
     * @param qps              每秒允许的请求数
     * @param defaultTimeoutMs 默认超时时间（毫秒）
     */
    public SimpleRateLimiter(int qps, long defaultTimeoutMs) {
        if (qps <= 0) {
            throw new IllegalArgumentException("QPS必须大于0");
        }
        if (defaultTimeoutMs <= 0) {
            throw new IllegalArgumentException("超时时间必须大于0");
        }

        this.qps = qps;
        this.tokens = qps; // 初始时令牌桶满
        this.lastRefillTime = System.currentTimeMillis();
        this.lastStatsTime = System.currentTimeMillis();
        this.defaultTimeoutMs = defaultTimeoutMs;

        log.info("QPS限流器初始化完成，QPS：{}，默认超时：{}ms", qps, defaultTimeoutMs);
    }

    /**
     * 获取一个令牌（使用默认超时时间）
     *
     * @throws RuntimeException 如果超时或被中断
     */
    public void acquire() {
        acquire(1);
    }

    /**
     * 获取指定数量的令牌（使用默认超时时间）
     *
     * @param permits 令牌数量
     * @throws RuntimeException 如果超时或被中断
     */
    public void acquire(int permits) {
        if (!acquire(permits, defaultTimeoutMs, TimeUnit.MILLISECONDS)) {
            throw new RuntimeException("获取令牌超时，等待时间：" + defaultTimeoutMs + "ms");
        }
    }

    /**
     * 获取一个令牌（带超时）
     *
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true如果获取成功，false如果超时
     */
    public boolean acquire(long timeout, TimeUnit unit) {
        return acquire(1, timeout, unit);
    }

    /**
     * 获取指定数量的令牌（带超时）
     *
     * @param permits 令牌数量
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true如果获取成功，false如果超时
     */
    public boolean acquire(int permits, long timeout, TimeUnit unit) {
        if (permits <= 0) {
            return true;
        }

        totalRequests++;
        long timeoutMs = unit.toMillis(timeout);
        long startTime = System.currentTimeMillis();
        long endTime = startTime + timeoutMs;

        synchronized (lock) {
            while (tokens < permits) {
                // 补充令牌
                refillTokens();

                // 再次检查是否有足够令牌
                if (tokens >= permits) {
                    break;
                }

                // 检查超时
                long currentTime = System.currentTimeMillis();
                if (currentTime >= endTime) {
                    log.warn("获取令牌超时，当前令牌：{}，需要令牌：{}，等待时间：{}ms",
                            tokens, permits, timeoutMs);
                    return false;
                }

                long remainingTime = endTime - currentTime;
                try {
                    // 计算下次补充令牌的时间，但最多等待1秒
                    long waitTime = Math.min(1000, remainingTime);
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("获取令牌时被中断", e);
                }
            }

            // 消费令牌
            tokens -= permits;
            successRequests++;
            long actualWaitTime = System.currentTimeMillis() - startTime;

            if (actualWaitTime > 1000) {
                log.info("获取到 {} 个令牌，剩余令牌：{}，等待时间：{}ms",
                        permits, tokens, actualWaitTime);
            } else {
                log.debug("获取到 {} 个令牌，剩余令牌：{}，等待时间：{}ms",
                        permits, tokens, actualWaitTime);
            }

            // 定期输出统计信息（每5分钟）
            printStatsIfNeeded();

            return true;
        }
    }

    /**
     * 尝试获取令牌（不阻塞）
     *
     * @return true如果获取成功，false如果需要等待
     */
    public boolean tryAcquire() {
        return tryAcquire(1);
    }

    /**
     * 尝试获取指定数量的令牌（不阻塞）
     *
     * @param permits 令牌数量
     * @return true如果获取成功，false如果令牌不足
     */
    public boolean tryAcquire(int permits) {
        if (permits <= 0) {
            return true;
        }

        totalRequests++;

        synchronized (lock) {
            // 先补充令牌
            refillTokens();

            if (tokens >= permits) {
                tokens -= permits;
                successRequests++;
                log.debug("立即获取到 {} 个令牌，剩余令牌：{}", permits, tokens);
                printStatsIfNeeded();
                return true;
            } else {
                log.debug("令牌不足，获取失败。当前令牌：{}，需要令牌：{}", tokens, permits);
                return false;
            }
        }
    }

    /**
     * 释放令牌（归还到令牌池）
     *
     * @param permits 要释放的令牌数量
     */
    public void release(int permits) {
        if (permits <= 0) {
            return;
        }

        synchronized (lock) {
            int newTokens = Math.min(qps, tokens + permits);
            int actualReleased = newTokens - tokens;
            tokens = newTokens;

            if (actualReleased > 0) {
                log.debug("释放了 {} 个令牌，当前令牌数：{}", actualReleased, tokens);
                lock.notifyAll(); // 唤醒等待的线程
            }
        }
    }

    /**
     * 释放一个令牌
     */
    public void release() {
        release(1);
    }

    /**
     * 重置令牌池（恢复到初始状态）
     */
    public void reset() {
        synchronized (lock) {
            tokens = qps;
            lastRefillTime = System.currentTimeMillis();
            totalRequests = 0;
            successRequests = 0;
            lastStatsTime = System.currentTimeMillis();
            log.info("令牌池已重置，当前令牌数：{}，统计信息已清零", tokens);
            lock.notifyAll(); // 重置时唤醒所有等待的线程
        }
    }

    /**
     * 补充令牌（基于时间自动补充）
     */
    private void refillTokens() {
        long now = System.currentTimeMillis();
        long timePassed = now - lastRefillTime;

        if (timePassed >= 1000) { // 每秒补充一次
            int tokensToAdd = (int) (timePassed / 1000) * qps;
            if (tokensToAdd > 0) {
                tokens = Math.min(qps, tokens + tokensToAdd);
                lastRefillTime = now;
                log.debug("补充令牌：{}，当前令牌数：{}", tokensToAdd, tokens);
            }
        }
    }

    /**
     * 获取当前可用令牌数
     * 线程安全的读取方法
     */
    public int getAvailableTokens() {
        synchronized (lock) {
            refillTokens(); // 先补充令牌
            return tokens;
        }
    }

    /**
     * 获取QPS设置（兼容原有capacity方法）
     */
    public int getCapacity() {
        return qps;
    }

    /**
     * 定期输出统计信息（每5分钟）
     */
    private void printStatsIfNeeded() {
        long now = System.currentTimeMillis();
        if (now - lastStatsTime > 5 * 60 * 1000 && totalRequests > 0) { // 5分钟
            double successRate = successRequests * 100.0 / totalRequests;
            log.info("📊 QPS统计 [5分钟] - 总请求: {}, 成功: {}, 成功率: {}%, QPS设置: {}, 当前令牌: {}",
                    totalRequests, successRequests, successRate, qps, tokens);

            if (successRate < 90) {
                log.warn("⚠️ QPS成功率较低 ({}%)，建议检查系统负载或考虑增加QPS限制", successRate);
            }

            lastStatsTime = now;
        }
    }

    /**
     * 获取统计信息（用于监控接口）
     */
    public String getStatsInfo() {
        if (totalRequests == 0) {
            return String.format("QPS: %d req/s, 当前令牌: %d, 暂无请求统计", qps, getAvailableTokens());
        }

        double successRate = successRequests * 100.0 / totalRequests;
        return String.format("QPS: %d req/s, 当前令牌: %d, 总请求: %d, 成功率: %.1f%%",
                qps, getAvailableTokens(), totalRequests, successRate);
    }

}
